/******************************************************************************
** 
** File name:	rtos_event.S
**	
** Description:	Basic event management. Implementation of the 
**				RTOS_SetEvent and RTOS_WaitEvent functions 
**				inherited services.
**
** RTOS ver:	3.0
**
** Modules/Prototype:
**
**		void	RTOS_WaitEvent			( RTOS_EVT_t * )
**		void	RTOS_WaitObjectEvent	( RTOS_OBJ_t * )
**		void	RTOS_WaitTimer			( RTOS_TMR_t * )
**		void	RTOS_WaitMessageAck		( RTOS_MSG_t * )
**		void	RTOS_WaitTask			( RTOS_TCB_t * )
**
**		void	RTOS_SetEvent			( RTOS_EVT_t * )
**		void	RTOS_SetObjectEvent		( RTOS_OBJ_t * )
**		void	RTOS_AckMessage			( RTOS_MSG_t * )
**		void	RTOS_SetObjectEventISR	( RTOS_OBJ_t * )
**		void	RTOS_SetEvenetISR		( RTOS_EVT_t * )
**
**		void 	RTOS_ResetObjectEvent	( RTOS_OBJ_t * )
**		void 	RTOS_ResetObjectEventISR( RTOS_OBJ_t * )
**		void 	RTOS_ResetEvent			( RTOS_EVT_t * )
**		void 	RTOS_ResetEventISR		( RTOS_EVT_t * )
**		
** Notes: none
**
*******************************************************************************
** History:
**
**	10/15/2011 - AK - Moving to RTOS v2. First prototype version.
**	10/25/2011 - AK - Adding RTOS_ResetEvent()
**	07/26/2012 - AK - RTOS API is downgraded to comply with earliest rtos v2
**  08/08/2012 - AK - RTOS source code is checked for byte-by-byte complience 
**					with MPP binary (Dec 8, 2011).
**	09/20/2012	- AK - 	RTOS v3. _ENTER_CRITICAL/_LEAVE_CRITICAL 
**								 _RTOS_PROLOG/_RTOS_EPILOG
** 		
******************************************************************************/
#include        "rtos.inc"

        _MODULE(rtos_event)

        _EXTERN(RtosKernelData)
        _EXTERN(_EnterISR)
        _EXTERN(_LeaveISR)
        _EXTERN(_EnqueueObj)
		_EXTERN(_AppendObj)
		_EXTERN(_RemoveObj)
		_EXTERN(_RemoveObjTop)
		_EXTERN(_RemoveObjEx)

        _CODESECTION

/******************************************************************************
;; Prototype:	
;;		void RTOS_WaitObjectEvent	( RTOS_OBJ_t * )
;;		void RTOS_WaitTimer			( RTOS_TMR_t * )
;;		void RTOS_WaitMessageAck	( RTOS_MSG_t * )
;;		void RTOS_WaitTask			( RTOS_TCB_t * )
;; Description:	Standard system objects have a event as their second byte.  
;;				This call waits on that event. Actually those calls are 
;;				equal to RTOS_WaitEvent(pObject->pEvent)
;; Passed:      p1h:p1l = ADDR(OBJECT)
;; Returns:		void
;; Uses:		Z, tmp0, tmp1
;; Stack:		TBD
;; Calls:		-
;; Assumes:		-
;; Notes:		Calls are allowed from user mode only, may block.
******************************************************************************/
    _FUNCTION(RTOS_WaitObjectEvent)
	
    _PUBLIC  (RTOS_WaitTimer)
    _PUBLIC  (RTOS_WaitMessageAck)
    _PUBLIC  (RTOS_WaitTask)

RTOS_WaitObjectEvent:

RTOS_WaitTimer:
RTOS_WaitMessageAck:
RTOS_WaitTask:

    subi    p1l, lo8(-2)
    sbci    p1h, hi8(-2)

    _ENDFUNC	;; _FUNCTION(RTOS_WaitObjectEvent)

/******************************************************************************
;; Prototype:	void RTOS_WaitEvent			( RTOS_EVT_t * )
;; Description:	Calling tasks will be queued up for an Event, which is
;;				similar to a Mutual Exclusion Event.  For just plain old 
;;				signaling only one task should wait on a event.
;; Passed:      p1h:p1l = ADDR(EVENT)
;; Returns:		void
;; Uses:		Z, tmp0, tmp1
;; Stack:		Context (one)
;; Calls:		_EnterISR, _LeaveISR, _RemoveObj, _AppendObj,
;; Assumes:		-
;; Notes:		Calls are allowed from user mode only, may block.
;;				Trivial case ~20 cycles
******************************************************************************/
    _FUNCTION(RTOS_WaitEvent)

RTOS_WaitEvent:

    mov     Zl, p1l						;; Zl:Zh = &(event)
    mov     Zh, p1h

    _ENTER_CRITICAL           			;; Must stay critical through entire routine

    ldd     tmp0, Z+NextL   			;; Get event
    ldd     tmp1, Z+NextH
    subi    tmp0, lo8(_DONE)
    sbci    tmp1, hi8(_DONE)			;; Test trivial case of _DONE

    brne    rwe01

	std     Z+NextL, tmp0   			;; Reset Event to _PEND
	std     Z+NextH, tmp1
	
	_LEAVE_CRITICAL						;; enable interrupts
	
	ret									;; and return

rwe01:

    _RTOS_PROLOG						;; Do task switch (interrupts are disabled)
	
	;; Here we must be at the top of the run queue !!!
	;;
	;; This is MOST CRITICAL place over the rtos code!!!
	;; The task is going to block on wait, but it is not
	;; yet removed from the running queue. If this place 
	;; is inetrrupted by ISR that sets event then we are
	;; getting conflict. ISR unblocks tasks, but it will 
	;; continue removing from the queue when returns 
	;; from ISR. 
	;;
	;; The problem is that event still be in SET state 
	;; and the waiting task will newer be resumed. Next 
	;; ISR will not SET event that is already in SET state.
	;;
	;; So the solution is to made _RTOS_PROLOG be critical 
	;; section and not to set I bit when leaving it. 
	;; Interrupts could be enabled (and must be enabled if 
	;; required) over the code after _RTOS_PROLOG call.
	
	
	_ENTER_CRITICAL						;; Must go critical again after entering the system level
	
	ldi     Zl, lo8(RtosKernelData + RunQueue)	;; Zl:Zh = &(RtosKernelData.queue)
    ldi     Zh, hi8(RtosKernelData + RunQueue)
    ldd     p2h, Z+Running+NextH				;; p2h:p2l = *(RtosKernelData->running)
    ldd     p2l, Z+Running+NextL
	
    rcall   _RemoveObj					;; Remove ourself from the run queue

    mov     Zl, p1l						;; Zl:Zh = &(evt)
    mov     Zh, p1h

    rcall   _AppendObj					;; Append ourselves to the Event

    _RTOS_EPILOG						;; Leave system level, restore task context 
										;; and finally end critical section

    _ENDFUNC	;; _FUNCTION(RTOS_WaitEvent)

/******************************************************************************
;; Prototype:	void	RTOS_SetObjectEvent	( RTOS_OBJ_t * )
;;				void	RTOS_AckMessage		( RTOS_MSG_t * )
;; Description:	Sets regular event or object' event.
;;				As the standard system objects have an event as their 
;;				second byte, this service is actually equal to 
;;				RTOS_SetEvent( pObject->pEvent ).
;; Passed:		R25:R24 = Object/Message
;; Returns:		none
;; Uses:		Everything :)
;; Stack:		TBD
;; Calls:		RTOS_SetEvent
;; Assumes:		
;; Notes:		Can be called from RTOS interrupt handler as well as
;;				from the task level. It just will not reschedule if 
;;				calling in kernel mode, or no task switch occured.
******************************************************************************/
        _FUNCTION(RTOS_SetObjectEvent)
        _PUBLIC(RTOS_AckMessage)

RTOS_AckMessage:
RTOS_SetObjectEvent:

        subi    p1l, lo8(-2)
        sbci    p1h, hi8(-2)

        _ENDFUNC	;; _FUNCTION(RTOS_SetObjectEvent)

/******************************************************************************
;; Prototype:	void	RTOS_SetEvent	( RTOS_EVT_t * )
;; Description:	Signals an object' event. If event is EVT_PEND or EVT_DONE, 
;;				just marks it EVT_DONE and return.  If a task is waiting, 
;;				then run it and set the event to the EVT_PEND state.
;; Passed:		R25:R24 = Event
;; Returns:		-
;; Uses:		Everything :)
;; Stack:		TBD
;; Calls:		RTOS_SetEventISR,_EnterISR,_LeaveISR
;; Assumes:		-
;; Notes:		Doesn't accumulate sets.  If event is set already, then
;;		        just ignore it (not a counting event)
******************************************************************************/
    _FUNCTION(RTOS_SetEvent)

RTOS_SetEvent:

	rcall   RTOS_SetEventISR			;; r1l == 0 if running task changed.

	;; -=AAK022=- lds	r1h, RtosKernelData + SysLevel
	;; -=AAK022=- inc	r1h						;; r1h == 0 if in task context
	;; -=AAK022=- or	r1l, r1h
	
	;; tst 	r1l							;; +=AAK022=+
	
	_ENTER_CRITICAL	;;
	
	lds		r1h, RtosKernelData+SysLevel
	inc		r1h							;; r1h == 0 if in task context
	or 		r1l, r1h
	
	breq    rtse0           			;; Reschedule if task context & queue changed (0).

	_LEAVE_CRITICAL

	ret									;; And return

rtse0:									;; Do task switch 					
	
	_ENTER_CRITICAL 					;; Begin critical section	(disable ALL interrupts)
		
	_RTOS_PROLOG						;; Save current task context, enter system level 
										;; and leave critical section
	_ENTER_CRITICAL 					;; Begin critical section again as it was ended 
										;; by _RTOS_PROLOG
	_RTOS_EPILOG						;; Leave system level and restore new running task context 
										;; critical section also ends here 
		

        _ENDFUNC	;; _FUNCTION(RTOS_SetEvent)

/******************************************************************************
;; Prototype:	void	RTOS_SetObjectEventISR	( RTOS_EVT_t * )
;; Description:	Interrupt version of the RTOS_SetObjectEvent()
;;				Actually calls RTOS_SetEvent( pObject->pEvent )
;; Passed:		p1h:p1l = pObject
;; Returns:		r1l != 0, nothing queued	
;;					 = 0, task queued
;; Uses:		everithing :)
;; Stack:		2
;; Calls:		-
;; Assumes:		-	
;; Notes:		Doesn't accumulate sets.  If event is set already, then
;;		        just ignore it (not a counting event)
******************************************************************************/
	_FUNCTION(RTOS_SetObjectEventISR)
		
RTOS_SetObjectEventISR:      ; This one here because so small

    subi    p1l, lo8(-2)
    sbci    p1h, hi8(-2)

    _ENDFUNC	;; _FUNCTION(RTOS_SetObjectEventISR)

/******************************************************************************
;; Prototype:	void	RTOS_SetEventISR		( RTOS_EVT_t * )
;; Description:	Interrupt version of the RTOS_SetEvent()
;; Passed:		p1h:p1l = pEvent
;; Returns:		r1l != 0, nothing queued	
;;					 = 0, task queued
;; Uses:		everithing :)
;; Stack:		TBD	
;; Calls:		TBD
;; Assumes:		Assumes p1l == r1l
;; Notes:		Doesn't accumulate sets.  If event already set, then
;;		        will just ignore it (not counting event)
******************************************************************************/
	_FUNCTION(RTOS_SetEventISR)

RTOS_SetEventISR:					

	mov     Zl, p1l
	mov     Zh, p1h
	ldi     p1l, lo8(_DONE)
	ldi     p1h, hi8(_DONE)

	_ENTER_CRITICAL				;; Begin critical section (all interrupts are disabled)

	ldd     p2h, Z+NextH
	ldd     p2l, Z+NextL
	cp      p1l, p2l
	cpc     p1h, p2h			;; if 0x02 or higher...
	
	brlo    rtise00 
	
	std     Z+NextL, p1l		;; Trivial case of _PEND or _DONE
	std     Z+NextH, p1h		;; Set to _DONE

BogusEvent:

	ldi     r1l, lo8(-1)		;; Nothing queued
	
	_LEAVE_CRITICAL				;; End critical section (upper lavel interrupts are enabled)	
	
	ret

rtise00:

	ldi     p1l, lo8(_LASTEV)	;; Check for valid event (e.g. >= $60)
	ldi     p1h, hi8(_LASTEV)	;; already zero...
	cp      p1l, p2l
	cpc     p1h, p2h
	
	brsh    BogusEvent

	rcall   _RemoveObjEx		;; Z->Prev, p2->Next (Object)

	mov     p1l, p2l
	mov     p1h, p2h
	
	rcall	_EnqueueObj       	;; p1h:p1l = Pid, r1l = queued status
	
	_LEAVE_CRITICAL				;; End critical section (upper lavel interrupts are enabled)	
	
	ret

	_ENDFUNC	;; _FUNCTION(RTOS_SetEventISR)

/******************************************************************************
;; Prototype:	RTOS_EVT_t * RTOS_ResetObjectEvent		( RTOS_OBJ_t * )
;;				RTOS_EVT_t * RTOS_ResetObjectEventISR	( RTOS_OBJ_t * )
;; Description:	Actually calls RTOS_ResetEevent( pObject->pEvent )
;; Passed:		R25:R24 = ADDR(Object)		
;; Returns:		-
;; Uses:		-
;; Stack:		TBD	
;; Calls:		RTOS_ResetEvent
;; Assumes:		-	
;; Notes:		Usable from both kernel or user mode.
******************************************************************************/
	_PUBLIC(RTOS_ResetObjectEventISR)
	_FUNCTION(RTOS_ResetObjectEvent)
        
RTOS_ResetObjectEvent:
RTOS_ResetObjectEventISR:

	mov     Zl, p1l
	mov     Zh, p1h
	
	adiw    Zl, 2
	
	rjmp    rtre00

	_ENDFUNC	;; _FUNCTION(RTOS_ResetObjectEvent)

/******************************************************************************
;; Prototype:	void	RTOS_ResetEvent			( RTOS_EVT_t * )
;;				void	RTOS_ResetEventISR		( RTOS_EVT_t * )
;; Description:	Force a event into the EVT_PEND state.  This is almost 
;;				identical to SetEvent, but the end state is always 
;;				EVT_PEND rather than, possibly, EVT_DONE
;; Passed:		R25:R24 = ADDR(Event)
;; Returns:		-
;; Uses:		Z, X, flags	
;; Stack:		TBD
;; Calls:		-
;; Assumes:		-	
;; Notes:		It doesn't make sense to reset an event that has
;;				a process waiting, so just skip that case.
******************************************************************************/
	_FUNCTION(RTOS_ResetEvent)
	_PUBLIC(RTOS_ResetEventISR)
        
RTOS_ResetEvent:
RTOS_ResetEventISR:

	mov     Zh, p1h
	mov     Zl, p1l
	
rtre00:

	_ENTER_CRITICAL						;; Begin critical section

	ldd     tmp2, Z+NextL
	ldd     tmp3, Z+NextH
	subi    tmp2, lo8(_DONE)
	sbci    tmp3, hi8(_DONE)
	
	breq    rtre01						;; If it was _DONE, now it is _PEND
										;; otherwize restore Pointer
	subi    tmp2, lo8(-_DONE)
	sbci    tmp3, hi8(-_DONE)
		
rtre01:

	std     Z+NextH, tmp3 
	std     Z+NextL, tmp2
	
	_LEAVE_CRITICAL						;; End critical section

	ret									;; and return

	_ENDFUNC	;; _FUNCTION(RTOS_ResetEvent)

		
		_END 	;; _MODULE(rtos_event)

